#!usr/bin/env python
# -*- coding:utf-8 _*-
"""
@author:zhengxin
@file: 02_basic_function.py
@time: 2022/10/14  14:53
# @describe: 5.2 基本功能
"""
import numpy as np
import pandas as pd

""" 重新索引 """
# pandas对象的一个重要方法是reindex，其作用是创建一个新对象，它的数据符合新的索引。看下面的例子：
obj = pd.Series([4.5, 7.2, -5.3, 3.6], index=['d', 'b', 'a', 'c'])
print(obj)


# 用该Series的reindex将会根据新索引进行重排。如果某个索引值当前不存在，就引入缺失值：
obj2 = obj.reindex(['a', 'b', 'c', 'd', 'e'])
print(obj2)

# 对于时间序列这样的有序数据，重新索引时可能需要做一些插值处理。method选项即可达到此目的，
# 例如，使用ffill可以实现前向值填充：
obj3 = pd.Series(['blue', 'purple', 'yellow'], index=[0, 2, 4])
print(obj3)
print(obj3.reindex(range(6), method='ffill'))


# 借助DataFrame，reindex可以修改（行）索引和列。只传递一个序列时，会重新索引结果的行：
frame = pd.DataFrame(np.arange(9).reshape((3, 3)), index=['a', 'c', 'd'],
                     columns=['Ohio', 'Texas', 'California'])
print(frame)
print(frame['Ohio'])
frame2 = frame.reindex(['a', 'b', 'c', 'd'])
print(frame2)

# 列可以用columns关键字重新索引：
states = ['Texas', 'Utah', 'California']
print(frame.reindex(columns=states))



""" 丢弃指定轴上的项 """
# 丢弃某条轴上的一个或多个项很简单，只要有一个索引数组或列表即可。由于需要执行一些数据整理和集合逻辑，
# 所以drop方法返回的是一个在指定轴上删除了指定值的新对象：
obj = pd.Series(np.arange(5.), index=['a', 'b', 'c', 'd', 'e'])
print('*' * 20)
print(obj)

new_obj = obj.drop('c')
print(new_obj)
print(obj.drop(['d', 'c']))


# 对于DataFrame，可以删除任意轴上的索引值。为了演示，先新建一个DataFrame例子：
data = pd.DataFrame(np.arange(16).reshape((4, 4)),
                    index=['Ohio', 'Colorado', 'Utah', 'New York'],
                    columns=['one', 'two', 'three', 'four'])
print("*" * 10)
print(data)


# 用标签序列调用 drop，会从行标签(axis 0)删除值
print(data.drop(['Colorado', 'Ohio']))

# 通过传递 axis=1 或 axis='columns'可以删除列的值
print(data.drop('two', axis=1))
print(data.drop(['two', 'four'], axis='columns'))


# 许多函数，如drop，会修改Series或DataFrame的大小或形状，可以就地修改对象，不会返回新的对象：
# 小心使用inplace，它会销毁所有被删除的数据。
obj.drop('c', inplace=True)
print(obj)




""" 索引、选取和过滤 """
# Series索引（obj[…]）的工作方式类似于NumPy数组的索引，只不过Series的索引值不只是整数。下面是几个例子：
obj = pd.Series(np.arange(4.), index=['a', 'b', 'c', 'd'])
print('*' * 10)
print(obj)
print(obj['b'])
print(obj[1])
print(obj[2:4])

print('*' * 10)
print(obj[['b', 'a', 'd']])
print(obj[[1, 3]])
print(obj[obj < 2])


# 利用标签的切片运算与普通的Python切片运算不同，其末端是包含的：
print(obj['b': 'c'])

# 用切片可以对Series的相应部分进行设置：
obj['b': 'c'] = 5
print(obj)

# 用一个值或序列对DataFrame进行索引其实就是获取一个或多个列：
data = pd.DataFrame(np.arange(16).reshape((4, 4)),
                    index=['Ohio', 'Colorado', 'Utah', 'New York'],
                    columns=['one', 'two', 'three', 'four'])
print('*' * 10)
print(data)
print(data['two'])
print(data[['three', 'one']])

# 这种索引方式有几个特殊的情况。首先通过切片或布尔型数组选取数据：
print(data[:2])
print(data[data['three'] > 5])

# 另一种用法是通过布尔型DataFrame（比如下面这个由标量比较运算得出的）进行索引：
print(data < 5)
data[data < 5] = 0
print(data)




""" 
用loc和iloc进行选取：
    对于DataFrame的行的标签索引，我引入了特殊的标签运算符loc和iloc。它们可以让你用类似NumPy的标记，
    使用轴标签（loc）或整数索引（iloc），从DataFrame选择行和列的子集。

"""
# 通过标签选择一行和多列：
print('*' * 20)
print(data.loc['Colorado', ['two', 'three']])

# 用iloc和整数进行选取-第2行的第3、第0、第1个：
print(data.iloc[2, [3, 0, 1]])

# 这两个索引函数也适用于一个标签或多个标签的切片：
print(data.loc[:'Utah', 'two'])
print(data.iloc[:, :3][data.three > 5])




""" 
整数索引:
    处理整数索引的pandas对象常常难住新手，因为它与Python内置的列表和元组的索引语法不同
"""
print('*' * 20)
ser = pd.Series(np.arange(3.))
print(ser)
# 会报错
# print(ser[-1])

# 另外，对于非整数索引，不会产生歧义：
ser2 = pd.Series(np.arange(3.), index=['a', 'b', 'c'])
print(ser2[-1])

# 为了进行统一，如果轴索引含有整数，数据选取总会使用标签。为了更准确，请使用loc（标签）或iloc（整数）：
print(ser[:1])
print(ser.loc[:1])
print(ser.iloc[:1])




""" 
 算术运算和数据对齐:
    pandas最重要的一个功能是，它可以对不同索引的对象进行算术运算。在将对象相加时，如果存在不同的索引对，则结果的索引就是该索引对的并集。
    对于有数据库经验的用户，这就像在索引标签上进行自动外连接。看一个简单的例子：

"""
print('*' * 20)
s1 = pd.Series([7.3, -2.5, 3.4, 1.5], index=['a', 'c', 'd', 'e'])
s2 = pd.Series([-2.1, 3.6, -1.5, 4, 3.1], index=['a', 'c', 'e', 'f', 'g'])
print(s1)
print(s2)

# 自动的数据对齐操作在不重叠的索引处引入了NA值。缺失值会在算术运算过程中传播。
print(s1+s2)

# 对于DataFrame，对齐操作会同时发生在行和列上：
df1 = pd.DataFrame(np.arange(9.).reshape((3, 3)), columns=list('bcd'),
                   index=['Ohio', 'Texas', 'Colorado'])
df2 = pd.DataFrame(np.arange(12.).reshape((4, 3)), columns=list('bde'),
                   index=['Utah', 'Ohio', 'Texas', 'Oregon'])
print(df1)
print(df2)
# 把它们相加后将会返回一个新的DataFrame，其索引和列为原来那两个DataFrame的并集：
print(df1+df2)

# 如果DataFrame对象相加，没有共用的列或行标签，结果都会是空：
df1 = pd.DataFrame({'A': [1, 2]})
df2 = pd.DataFrame({'B': [3, 4]})
print(df1)
print(df2)
print(df1-df2)



""" 
在算术方法中填充值:
    在对不同索引的对象进行算术运算时，你可能希望当一个对象中某个轴标签在另一个对象中找不到时填充一个特殊值（比如0）：

"""
df1 = pd.DataFrame(np.arange(12.).reshape((3, 4)), columns=list('abcd'))
df2 = pd.DataFrame(np.arange(20.).reshape((4, 5)), columns=list('abcde'))
df2.loc[1, 'b'] = np.nan
print('*' * 20)
print(df1)
print(df2)

# 将它们相加时，没有重叠的位置就会产生NA值：
print(df1+df2)


# 使用df1的add方法，传入df2以及一个fill_value参数--得到两者相加的数值：
print(df1.add(df2, fill_value=0))

# 除法
print(1/df1)
print(df1.rdiv(1))


# 与此类似，在对Series或DataFrame重新索引时，也可以指定一个填充值：
print(df1.reindex(columns=df2.columns, fill_value=0))




"""
DataFrame和Series之间的运算:
    跟不同维度的NumPy数组一样，DataFrame和Series之间算术运算也是有明确规定的。先来看一个具有启发性的例子，
    计算一个二维数组与其某行之间的差：

"""
arr = np.arange(12.).reshape((3, 4))
print("****************************")
print(arr)
print(arr[0])
# 当我们从arr减去arr[0]，每一行都会执行这个操作。这就叫做广播（broadcasting）
print(arr - arr[0])


frame = pd.DataFrame(np.arange(12.).reshape((4, 3)),
                     columns=list('bde'),
                     index=['Utah', 'Ohio', 'Texas', 'Oregon'])
series = frame.iloc[0]
print(frame)
print(series)
# 默认情况下，DataFrame和Series之间的算术运算会将Series的索引匹配到DataFrame的列，然后沿着行一直向下广播：
print(frame - series)


# 如果某个索引值在DataFrame的列或Series的索引中找不到，则参与运算的两个对象就会被重新索引以形成并集：
series2 = pd.Series(range(3), index=['b', 'e', 'f'])
print(frame + series2)

# 如果你希望匹配行且在列上广播，则必须使用算术运算方法。例如：
series3 = frame['d']
print(frame)
print(series3)
# 传入的轴号就是希望匹配的轴。在本例中，我们的目的是匹配DataFrame的行索引（axis=’index’ or axis=0）并进行广播。
print(frame.sub(series3, axis='index'))




"""
函数应用和映射:
    NumPy的ufuncs（元素级数组方法）也可用于操作pandas对象：

"""
frame = pd.DataFrame(np.random.randn(4, 3), columns=list('bde'),
                     index=['Utah', 'Ohio', 'Texas', 'Oregon'])
print(frame)
# abs函数-返回x（数字）的绝对值。
print(np.abs(frame))

# 另一个常见的操作是，将函数应用到由各列或行所形成的一维数组上。DataFrame的apply方法即可实现此功能：
# 这里的函数f，计算了一个Series的最大值和最小值的差，在frame的每列都执行了一次。结果是一个Series，使用frame的列作为索引。
f = lambda x: x.max() - x.min()
print(frame.apply(f))

# 如果传递axis=’columns’到apply，这个函数会在每行执行：
print(frame.apply(f, axis='columns'))

# 传递到apply的函数不是必须返回一个标量，还可以返回由多个值组成的Series：
def f(x):
    return pd.Series([x.min(), x.max()], index=['min', 'max'])

print(frame.apply(f))


# 元素级的Python函数也是可以用的。假如你想得到frame中各个浮点值的格式化字符串，使用applymap即可：
format = lambda x: '%.2f' % x
print(frame.applymap(format))

# 之所以叫做applymap，是因为Series有一个用于应用元素级函数的map方法：
print(frame['e'].map(format))




""" 
排序和排名：
   根据条件对数据集排序（sorting）也是一种重要的内置运算。要对行或列索引进行排序（按字典顺序），可使用sort_index方法，
   它将返回一个已排序的新对象：

"""
obj = pd.Series(range(4), index=['d', 'a', 'b', 'c'])
print(obj.sort_index())

# 对于DataFrame，则可以根据任意一个轴上的索引进行排序：
frame = pd.DataFrame(np.arange(8).reshape((2, 4)),
                     index=['three', 'one'],
                     columns=['d', 'a', 'b', 'c'])
print("****************************")
# 按升序排序
print(frame.sort_index())
# 列字母顺序排序
print(frame.sort_index(axis=1))

# 数据默认是按升序排序的，但也可以降序排序：
print(frame.sort_index(axis=1, ascending=False))

# 若要按值对Series进行排序，可使用其sort_values方法：
obj = pd.Series([4, 7, -3, 2])
print(obj)
# 按值升序
print(obj.sort_values())


# 在排序时，任何缺失值默认都会被放到Series的末尾：
obj = pd.Series([4, np.nan, 7, np.nan, -3, 2])
print(obj.sort_values())

# 当排序一个DataFrame时，你可能希望根据一个或多个列中的值进行排序。将一个或多个列的名字传递给sort_values的by选项即可达到该目的：
frame = pd.DataFrame({'b': [4, 7, -3, 2], 'a': [0, 1, 0, 1]})
print(frame)
print(frame.sort_values(by='b'))
# 要根据多个列进行排序，传入名称的列表即可：
print(frame.sort_values(by=['a', 'b']))


# 排名会从1开始一直到数组中有效数据的数量。接下来介绍Series和DataFrame的rank方法。默认情况下，rank是通过“为
# 各组分配一个平均排名”的方式破坏平级关系的：
obj = pd.Series([7, -5, 7, 4, 2, 0, 4])
print(obj.rank())

# 也可以根据值在原数据中出现的顺序给出排名-
# 这里，条目0和2没有使用平均排名6.5，它们被设成了6和7，因为数据中标签0位于标签2的前面。
print(obj.rank(method='first'))

# 你也可以按降序进行排名：
print(obj.rank(ascending=False, method='max'))


# DataFrame可以在行或列上计算排名：
frame = pd.DataFrame({'b': [4.3, 7, -3, 2], 'a': [0, 1, 0, 1], 'c': [-2, 5, 8, -2.5]})
print(frame)
print(frame.rank(axis='columns'))






""" 
带有重复标签的轴索引：
        直到目前为止，我所介绍的所有范例都有着唯一的轴标签（索引值）。虽然许多pandas函数（如reindex）都要求标签唯一，
        但这并不是强制性的。我们来看看下面这个简单的带有重复索引值的Series：

 """
obj = pd.Series(range(5), index=['a', 'a', 'b', 'b', 'c'])
print("******************")
print(obj)
# 索引的is_unique属性可以告诉你它的值是否是唯一的：
print(obj.index.is_unique)

# 对于带有重复值的索引，数据选取的行为将会有些不同。如果某个索引对应多个值，则返回一个Series；而对应单个值的，则返回一个标量值：
print(obj['a'])
print(obj['c'])

# 对DataFrame的行进行索引时也是如此：
df = pd.DataFrame(np.random.randn(4, 3), index=['a', 'a', 'b', 'b'])
print(df)
print(df.loc['b'])